#include "ServerCase.h"
#include "framework.h"
#include <list>
#include <mutex>
#include <algorithm>

bool Proxy_record = false;
sqlite3* mysql;
const char* record_dir = "record";
const char* record_database = "default.db";

std::list<t_recode_info*>* recordList = NULL;
std::mutex* recordListLock = NULL;
std::list<t_recode_info*>* newrecordList = NULL;

std::map<std::string, bool>* record_filter = new std::map<std::string, bool>;

const char* get_project_name_by_project_id(int project_id) {
	char key[4] = { 0x0 };
	snprintf(key, sizeof(key), "%d", project_id);
	return config_get_string_value("project", key, NULL);
}

static bool cratet_case_info_table(sqlite3* dbConn){
	char* errmsg = NULL;
	char sql[512] = { 0x0 };
	snprintf(sql, sizeof(sql), "CREATE TABLE 'caseinfo' (\
                'id'  INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\
                'project_id'  INT8,\
                'project_name'  TEXT,\
                'des'  TEXT);");
	int ret = sqlite3_exec(dbConn, sql, NULL, NULL, &errmsg);
	if (ret != SQLITE_OK){
		char* p = strstr(errmsg, "already exists");
		if (p == NULL)
			LOG(slogid, LOG_ERROR, "%s:%d sqlite3 error[%s]\r\n", __func__, __LINE__, errmsg);
		return false;
	}
	return true;
}

void case_info_init(sqlite3* dbConn, int projectid, const char* project, const char* des){
	if (cratet_case_info_table(dbConn) == false) return;
	char* errmsg = NULL;
	char sql[512] = { 0x0 };
	snprintf(sql, sizeof(sql), "insert into 'caseinfo' (project_id, project_name, des) values(%d, '%s', '%s')", projectid, project, des);
	int ret = sqlite3_exec(dbConn, sql, NULL, NULL, &errmsg);
	if (ret != SQLITE_OK){
		LOG(slogid, LOG_ERROR, "%s:%d sqlite3 error[%s]\r\n", __func__, __LINE__, errmsg);
	}
}

void case_info_update(sqlite3* dbConn, int projectid, const char* project, const char* des){
	char* errmsg = NULL;
	char sql[512] = { 0x0 };
	snprintf(sql, sizeof(sql), "update 'caseinfo' set project_id = %d, project_name = '%s',des = '%s' where id = 1", projectid, project, des);
	int ret = sqlite3_exec(dbConn, sql, NULL, NULL, &errmsg);
	if (ret != SQLITE_OK){
		LOG(slogid, LOG_ERROR, "%s:%d sqlite3 error[%s]\r\n", __func__, __LINE__, errmsg);
		case_info_init(dbConn, projectid, project, des);
	}
}

bool server_default_case_init() {
	recordList = new(std::nothrow) std::list<t_recode_info*>;
	recordListLock = new(std::nothrow) std::mutex;
	newrecordList = new(std::nothrow) std::list<t_recode_info*>;
	if (recordList == NULL || recordListLock == NULL || newrecordList == NULL)
	{
		if (recordList) delete recordList;
		if (recordListLock) delete recordListLock;
		if (newrecordList)	delete newrecordList;
		return  false;
	}
	char filepath[256] = { 0x0 };
	snprintf(filepath, sizeof(filepath), "%s/%s/%s", CasePath, record_dir, record_database);
	CreateFileDirectory(filepath);
	sqlite3_open(filepath, &mysql);
	case_info_init(mysql, 0, get_project_name_by_project_id(0), "默认用例");
	sqlite3_exec(mysql, "PRAGMA synchronous = OFF; ", 0, 0, 0);
	return true;
}

int create_case_name(int project_id, char* buf, size_t size) {
	time_t now = NOWTIME;
	struct tm tmm;
#ifdef __WINDOWS__
	localtime_s(&tmm, &now);
#else
	localtime_r(&now, &tmm);
#endif // __WINDOWS__
	snprintf(buf, size, "P%d_%04d%02d%02d%02d%02d%02d.db", project_id, tmm.tm_year + 1900, tmm.tm_mon + 1, tmm.tm_mday, tmm.tm_hour, tmm.tm_min, tmm.tm_sec);
	return 0;
}

bool default_case_change_name(const char* case_dir, const char* new_name){
	if (Proxy_record == true) return  false;

	char oldfile[256] = { 0x0 };
	char newfile[256] = { 0x0 };
	snprintf(oldfile, sizeof(oldfile), "%s/%s/%s", CasePath, record_dir, record_database);
	snprintf(newfile, sizeof(newfile), "%s/%s/%s", CasePath, case_dir, new_name);

	sqlite3_close(mysql);
	int ret = rename(oldfile, newfile);

	sqlite3_open(oldfile, &mysql);
	case_info_init(mysql, 0, get_project_name_by_project_id(0), "默认用例");
	sqlite3_exec(mysql, "PRAGMA synchronous = OFF; ", 0, 0, 0);
	return ret == 0 ? true : false;
}

void insert_msg_to_record_list(const char* ip, int port, unsigned long long sessionid, int event_type, int protocol, const char* msg, int len)
{
	t_recode_info* info = (t_recode_info*)malloc(sizeof(t_recode_info));
	if (info == NULL) return;
	memset(info, 0x0, sizeof(t_recode_info));
	info->time = GetSysTimeMicros();
	snprintf(info->ip, sizeof(info->ip), "%s", ip);
	info->port = port;
	info->sessionid = sessionid;
	info->event = event_type;
	info->protocol = protocol;
	if (msg){
		info->msg = (char*)malloc(len);
		if (info->msg == NULL){
			free(info);
			return;
		}
		memcpy(info->msg, msg, len);
		info->msglen = len;
	}
	recordListLock->lock();
	recordList->push_back(info);
	recordListLock->unlock();
}

static bool get_base64_string(char** content, size_t* len, const char* src, int srclen){
	if (src){
		char* data = base64encode(src, srclen, len);
		if (!data) return false;
		*content = data;
	}
	else{
		*content = (char*)malloc(4);
		if (*content == NULL) return false;
		memset(*content, 0x0, 4);
	}
	return true;
}

static bool cratet_table(sqlite3* dbconn, const char* tablename){
	char* errmsg = NULL;
	char sql[512] = { 0x0 };
	snprintf(sql, sizeof(sql), "CREATE TABLE '%s' (\
                'id'  INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\
                'recordtime'  INT8,\
                'event_type'  INTEGER,\
				'protocol'  INTEGER,\
                'ip'  TEXT,\
                'port'  INTEGER,\
				'sessionid' INT8,\
                'content'  TEXT,\
				'note'  TEXT);", tablename);
	int ret = sqlite3_exec(dbconn, sql, NULL, NULL, &errmsg);
	if (ret != SQLITE_OK){
		char* p = strstr(errmsg, "already exists");
		if (p == NULL) LOG(slogid, LOG_ERROR, "%s:%d sqlite3 error[%s]\r\n", __func__, __LINE__, errmsg);
		return false;
	}
	return true;
}

static int insert_table(sqlite3* dbconn, const char* tablename, long long time, int event, int protocol, const char* ip, int port, unsigned long long sessionid, const char* msg, int clen, const char* note, int nlen, int flag){
	char* errmsg = NULL;
	char* cB64 = NULL, *nB64 = NULL;
	size_t cB64len = 0, nB64len = 0;
	bool res = get_base64_string(&cB64, &cB64len, msg, clen);
	if (res == false) return -1;
	res = get_base64_string(&nB64, &nB64len, note, nlen);
	if (res == false) {
		free(cB64);
		return -1;
	}

	size_t alloc_len = cB64len + nB64len + 256;
	char* sql = (char*)malloc(alloc_len);
	if (sql == NULL){
		free(cB64);
		free(nB64);
		return -2;
	}
	snprintf(sql, alloc_len, "insert into '%s' (recordtime, event_type, protocol, ip, port, sessionid, content, note) \
		values(%lld, %d, %d, '%s', %d, %llu, '%s', '%s')",
		tablename, time, event, protocol, ip, port, sessionid, cB64, nB64);
	
	int ret = sqlite3_exec(dbconn, sql, NULL, NULL, &errmsg);
	if (ret != SQLITE_OK){
		if (flag){
			LOG(slogid, LOG_ERROR, "%s:%d sqlite3 error[%s]\r\n", __func__, __LINE__, errmsg);
			LOG(slogid, LOG_ERROR, "%s:%d [%s]\r\n", __func__, __LINE__, sql);
		}
		free(cB64);
		free(nB64);
		free(sql);
		return -3;
	}
	free(cB64);
	free(nB64);
	free(sql);
	return 0;
}

int ip_port_to_table(const char* ip, int port, char* buf, size_t size) {
	int offset = 0;
	if (strchr(ip, ':')) {
		offset = snprintf(buf, size, "step_[%s]:%d", ip, port);
	}
	else {
		offset = snprintf(buf, size, "step_%s:%d", ip, port);
	}
	return offset;
}

int addr_to_table(const char* addr, char* buf, size_t size) {
	return snprintf(buf, size, "step_%s", addr);;
}

int table_to_addr(const char* table, char* buf, size_t size) {
	int offset = 0;
	const char* p = strstr(table, "_");
	if (p) {
		offset = snprintf(buf, size, p + 1);
	}
	return offset;
}

int case_append(sqlite3* dbconn, long long time, int event, int protocol, const char* ip, int port, unsigned long long sessionid, const char* content, int clen, const char* note, int nlen) {
	char tablename[128] = { 0x0 };
	ip_port_to_table(ip, port, tablename, sizeof(tablename));

	int ret = insert_table(dbconn, tablename, time, event, protocol, ip, port, sessionid, content, clen, note, nlen, 0);
	if (ret != 0){
		if (cratet_table(dbconn, tablename) == true){
			ret = insert_table(dbconn, tablename, time, event, protocol, ip, port, sessionid, content, clen, note, nlen, 1);
			if (ret != 0) LOG(slogid, LOG_ERROR, "%s:%d insert error\n", __func__, __LINE__);
		}
		else{
			LOG(slogid, LOG_ERROR, "%s:%d create error\n", __func__, __LINE__);
		}
	}
	return 0;
}

void insert_msg_recodr_db(){
	if (recordList == NULL) return;
	static time_t lasttime = NOWTIME;
	if ((recordList->size() > 10) || (recordList->size() > 0 && NOWTIME- lasttime > 1)){
		recordListLock->lock();
		std::list<t_recode_info*>* oldlist = recordList;
		recordList = newrecordList;
		newrecordList = oldlist;
		recordListLock->unlock();

		sqlite3_exec(mysql, "begin;", 0, 0, 0);
		std::list<t_recode_info*>::iterator iter = oldlist->begin();
		for (; iter != oldlist->end(); ++iter){
			t_recode_info* info = *iter;
			if (record_filter->find(info->ip) == record_filter->end())
				case_append(mysql, info->time, info->event, info->protocol, info->ip, info->port, info->sessionid, info->msg, info->msglen, "", 0);

			if (info->msg) free(info->msg);
			free(info);
		}
		sqlite3_exec(mysql, "commit;", 0, 0, 0);
		oldlist->clear();
		lasttime = NOWTIME;
	}
}